﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Scripting;

namespace DlrBasic.Runtime
{
    /// <summary>
    /// BASIC memory representation. Trying to create default variables
    /// per request. Names are not case sensitive, arrays shall have either
    /// index or bounds array. It will give number of dimensions. The memory 
    /// can be cleaned by one operation <code>Clear</code>.
    /// 
    /// Access to variables made via SetXXX and GetXXX methods. <code>index</code> 
    /// parameter shall be specied only for array variables.
    /// 
    /// MAT operations may access arrays by partial name of the array 
    /// (w/o specifying the suffix).
    /// </summary>
    public sealed class DlrBasicMemory
    {
        Dictionary<SymbolId, object> values;
        List<Dictionary<SymbolId, Array>> arrays;

        public object this[SymbolId name]
        {
            get { return GetObject(name, typeof(object), null); }
            set { SetObject(name, null, value); }
        }

        public object this[string name]
        {
            get { return GetObject(SymbolTable.StringToCaseInsensitiveId(name), typeof(object), null); }
            set { SetObject(SymbolTable.StringToCaseInsensitiveId(name), null, value); }
        }

        public DlrBasicMemory()
        {
            Clear();
        }

        public void Clear()
        {
            values = new Dictionary<SymbolId, object>();
            arrays = new List<Dictionary<SymbolId, Array>>();
        }

        public short GetInt(SymbolId name, int[] index)
        {
            return (short)GetObject(name, typeof(short), index);
        }

        public float GetSingle(SymbolId name, int[] index)
        {
            return (float)GetObject(name, typeof(float), index);
        }

        public double GetDouble(SymbolId name, int[] index)
        {
            return (double)GetObject(name, typeof(double), index);
        }

        public string GetString(SymbolId name, int[] index)
        {
            return (string)GetObject(name, typeof(string), index);
        }

        public void SetInt(SymbolId name, int[] index, short value)
        {
            SetObject(name, index, value);
        }

        public void SetSingle(SymbolId name, int[] index, float value)
        {
            SetObject(name, index, value);
        }

        public void SetDouble(SymbolId name, int[] index, double value)
        {
            SetObject(name, index, value);
        }

        public void SetString(SymbolId name, int[] index, string value)
        {
            SetObject(name, index, value);
        }

        public void ZeroArray(SymbolId name)
        {
            for (int i = 0; i < arrays.Count; i++)
            {
                if (arrays[i].ContainsKey(name))
                {
                    Array array = arrays[i][name];
                    Type itemType = array.GetType().GetElementType();
                    InitializeArray(itemType, array);
                    break;
                }
            }
        }

        private object CreateDefaultValue(Type itemType)
        {
            if(itemType == typeof(string))
                return String.Empty;
            else
                return Activator.CreateInstance(itemType);
        }

        private Array CreateDefaultArray(Type itemType, int dimension)
        {
            int[] defaultLengths = new int[dimension];
            for (int i = 0; i < defaultLengths.Length; i++)
            {
                defaultLengths[i] = 11;
            }

            Array array = Array.CreateInstance(itemType, defaultLengths);
            InitializeArray(itemType, array);
            return array;
        }

        private void InitializeArray(Type itemType, Array array)
        {
            object defaultValue = CreateDefaultValue(itemType);
            int[] index = new int[array.Rank];            
            do
            {
                int j = index.Length - 1;
                array.SetValue(defaultValue, index);
                while (j >= 0 && index[j] == array.GetUpperBound(j))
                {
                    index[j--] = 0;
                }
                if (j < 0) break;
                ++index[j];
            } while (true);
        }

        public object GetObject(SymbolId name, Type itemType, int[] index)
        {
            if (index == null || index.Length == 0)
            {
                object obj;
                if (!values.TryGetValue(name, out obj))
                {
                    values.Add(name, obj = CreateDefaultValue(itemType));
                }
                return obj;
            }
            else
            {
                int dimension = index.Length;
                Array array;
                if(dimension > arrays.Count || !arrays[dimension - 1].TryGetValue(name, out array))
                {
                    while (dimension > arrays.Count)
                    {
                        arrays.Add(new Dictionary<SymbolId, Array>());
                    }
                    arrays[dimension - 1].Add(name, array = CreateDefaultArray(itemType, dimension));
                }
                return array.GetValue(index);
            }
        }

        public void SetObject(SymbolId name, int[] index, object value)
        {
            Type itemType = value.GetType();
            if (index == null || index.Length == 0)
            {
                values[name] = value;
            }
            else
            {
                int dimension = index.Length;
                Array array;
                if (dimension > arrays.Count || !arrays[dimension - 1].TryGetValue(name, out array))
                {
                    while (dimension > arrays.Count)
                    {
                        arrays.Add(new Dictionary<SymbolId, Array>());
                    }
                    arrays[dimension - 1].Add(name, array = CreateDefaultArray(itemType, dimension));
                }
                array.SetValue(value, index);
            }
        }

        public void Set<T>(SymbolId name, T value)
        {
            SetObject(name, null, value);
        }

        public void Set<T>(SymbolId name, int[] index, T value)
        {
            SetObject(name, index, value);
        }

        public void Set<T>(string name, T value)
        {
            SetObject(SymbolTable.StringToCaseInsensitiveId(name), null, value);
        }

        public void Set<T>(string name, int[] index, T value)
        {
            SetObject(SymbolTable.StringToCaseInsensitiveId(name), index, value);
        }

        public T Get<T>(SymbolId name)
        {
            return (T)GetObject(name, typeof(T), null);
        }

        public T Get<T>(SymbolId name, int[] index)
        {
            return (T)GetObject(name, typeof(T), index);
        }

        public T Get<T>(string name)
        {
            return (T)GetObject(SymbolTable.StringToCaseInsensitiveId(name), typeof(T), null);
        }

        public T Get<T>(string name, int[] index)
        {
            return (T)GetObject(SymbolTable.StringToCaseInsensitiveId(name), typeof(T), index);
        }

        public void InitArray(SymbolId name, Type itemType, int[] bounds)
        {
            int dimension = bounds.Length;
            int[] defaultLengths = new int[dimension];
            for (int i = 0; i < dimension; i++)
            {
                defaultLengths[i] = bounds[i] + 1;
            }
            Array array = Array.CreateInstance(itemType, defaultLengths);
            InitializeArray(itemType, array);

            while (dimension > arrays.Count)
            {
                arrays.Add(new Dictionary<SymbolId, Array>());
            }
            arrays[dimension - 1][name] = array;
        }
    }
}
